--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit ff4e4488d1eb6e84eca4010c73fa4824541e95ab
Parents : d05360b
Author : Ivan <e46112d44649266d71fe2193e00a4710>
Signature : T66BB85Valid, signed by author
Date : 2026-07-26T06:24:48-05:00
feat: fix map drawing management by scoping delete and update operations to identity_hash, and add corresponding tests
Changes
4 files changed, 125 insertions(+), 10 deletions(-)
Diff
diff --git a/meshchatx.rsm b/meshchatx.rsm
index 53763ad0..9d8d4296 100644
Binary files a/meshchatx.rsm and b/meshchatx.rsm differ
diff --git a/meshchatx/src/backend/database/map_drawings.py b/meshchatx/src/backend/database/map_drawings.py
index d3593b23..38271dcd 100644
--- a/meshchatx/src/backend/database/map_drawings.py
+++ b/meshchatx/src/backend/database/map_drawings.py
@@ -37,15 +37,21 @@ class MapDrawingsDAO:
(identity_hash,),
)
- def delete_drawing(self, drawing_id):
- self.provider.execute(
- "DELETE FROM map_drawings WHERE id = ?",
- (drawing_id,),
+ def delete_drawing(self, drawing_id, identity_hash: str) -> bool:
+ cur = self.provider.execute(
+ "DELETE FROM map_drawings WHERE id = ? AND identity_hash = ?",
+ (drawing_id, identity_hash),
)
+ return cur.rowcount > 0
- def update_drawing(self, drawing_id, name, data):
+ def update_drawing(self, drawing_id, identity_hash: str, name, data) -> bool:
now = datetime.now(UTC)
- self.provider.execute(
- "UPDATE map_drawings SET name = ?, data = ?, updated_at = ? WHERE id = ?",
- (name, data, now, drawing_id),
+ cur = self.provider.execute(
+ """
+ UPDATE map_drawings
+ SET name = ?, data = ?, updated_at = ?
+ WHERE id = ? AND identity_hash = ?
+ """,
+ (name, data, now, drawing_id, identity_hash),
)
+ return cur.rowcount > 0
diff --git a/meshchatx/src/backend/http/routes/map.py b/meshchatx/src/backend/http/routes/map.py
index b0cef51c..290139f0 100644
--- a/meshchatx/src/backend/http/routes/map.py
+++ b/meshchatx/src/backend/http/routes/map.py
@@ -232,17 +232,31 @@ def register_map_routes(routes, app):
@routes.delete("/api/v1/map/drawings/{drawing_id}")
async def delete_map_drawing(request):
+ identity_hash = app.identity.hash.hex()
drawing_id = request.match_info.get("drawing_id")
- app.database.map_drawings.delete_drawing(drawing_id)
+ deleted = app.database.map_drawings.delete_drawing(
+ drawing_id,
+ identity_hash,
+ )
+ if not deleted:
+ return web.json_response({"error": "Drawing not found"}, status=404)
return web.json_response({"message": "Drawing deleted successfully"})
@routes.patch("/api/v1/map/drawings/{drawing_id}")
async def update_map_drawing(request):
+ identity_hash = app.identity.hash.hex()
drawing_id = request.match_info.get("drawing_id")
data = await request.json()
name = data.get("name")
drawing_data = data.get("data")
- app.database.map_drawings.update_drawing(drawing_id, name, drawing_data)
+ updated = app.database.map_drawings.update_drawing(
+ drawing_id,
+ identity_hash,
+ name,
+ drawing_data,
+ )
+ if not updated:
+ return web.json_response({"error": "Drawing not found"}, status=404)
return web.json_response({"message": "Drawing updated successfully"})
@routes.get("/api/v1/map/overlays")
diff --git a/tests/backend/test_map_drawings_oracle.py b/tests/backend/test_map_drawings_oracle.py
new file mode 100644
index 00000000..f1bf89af
--- /dev/null
+++ b/tests/backend/test_map_drawings_oracle.py
@@ -0,0 +1,95 @@
+# SPDX-License-Identifier: 0BSD
+"""Oracles for map drawing ownership (identity_hash column)."""
+
+from __future__ import annotations
+
+from datetime import UTC, datetime
+
+import pytest
+
+from meshchatx.src.backend.database import Database
+from meshchatx.src.backend.database.provider import DatabaseProvider
+from meshchatx.src.backend.database.schema import DatabaseSchema
+
+
+@pytest.fixture
+def db(tmp_path):
+ path = str(tmp_path / "map_drawings_oracle.db")
+ provider = DatabaseProvider(path)
+ DatabaseSchema(provider).initialize()
+ database = Database(path)
+ yield database
+ database.close_all()
+ provider.close_all()
+
+
+def test_oracle_delete_drawing_scoped_to_identity_hash(db):
+ owner_a = "aa" * 16
+ owner_b = "bb" * 16
+ now = datetime.now(UTC)
+ db.provider.execute(
+ """
+ INSERT INTO map_drawings (identity_hash, name, data, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (owner_a, "a-line", "{}", now, now),
+ )
+ row_b = db.provider.execute(
+ """
+ INSERT INTO map_drawings (identity_hash, name, data, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (owner_b, "b-line", "{}", now, now),
+ )
+ drawing_b_id = row_b.lastrowid
+
+ assert db.map_drawings.delete_drawing(drawing_b_id, owner_a) is False
+ assert len(db.map_drawings.get_drawings(owner_b)) == 1
+ assert db.map_drawings.delete_drawing(drawing_b_id, owner_b) is True
+ assert db.map_drawings.get_drawings(owner_b) == []
+
+
+def test_oracle_update_drawing_scoped_to_identity_hash(db):
+ owner_a = "cc" * 16
+ owner_b = "dd" * 16
+ now = datetime.now(UTC)
+ db.provider.execute(
+ """
+ INSERT INTO map_drawings (identity_hash, name, data, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (owner_a, "shared-name", '{"a":1}', now, now),
+ )
+ row_b = db.provider.execute(
+ """
+ INSERT INTO map_drawings (identity_hash, name, data, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (owner_b, "b-shape", '{"b":1}', now, now),
+ )
+ drawing_b_id = row_b.lastrowid
+
+ assert (
+ db.map_drawings.update_drawing(
+ drawing_b_id,
+ owner_a,
+ "hijacked",
+ '{"x":1}',
+ )
+ is False
+ )
+ rows = db.map_drawings.get_drawings(owner_b)
+ assert len(rows) == 1
+ assert rows[0]["name"] == "b-shape"
+
+ assert (
+ db.map_drawings.update_drawing(
+ drawing_b_id,
+ owner_b,
+ "renamed",
+ '{"b":2}',
+ )
+ is True
+ )
+ rows = db.map_drawings.get_drawings(owner_b)
+ assert rows[0]["name"] == "renamed"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────